/*
 ******************************************************************************
 * BEGIN LICENSE BLOCK *** Version: CPL 1.0/GPL 2.0/LGPL 2.1
 * 
 * The contents of this file are subject to the Common Public License Version
 * 1.0 (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 * 
 * Copyright (C) 2006 Charles Oliver Nutter <headius@headius.com>
 * Copytight (C) 2006-2007 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2007 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2007 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in
 * which case the provisions of the GPL or the LGPL are applicable instead of
 * those above. If you wish to allow use of your version of this file only under
 * the terms of either the GPL or the LGPL, and not to allow others to use your
 * version of this file under the terms of the CPL, indicate your decision by
 * deleting the provisions above and replace them with the notice and other
 * provisions required by the GPL or the LGPL. If you do not delete the
 * provisions above, a recipient may use your version of this file under the
 * terms of any one of the CPL, the GPL or the LGPL. END LICENSE BLOCK ****
 ******************************************************************************/

package asruby.evaluator {

/* import asruby.*;
import asruby.internal.runtime.methods.WrapperMethod;
import asruby.javasupport.util.RuntimeHelpers;
import asruby.parser.StaticScope;
import asruby.runtime.Block;
import asruby.runtime.CallType;
import asruby.runtime.DynamicScope;
import asruby.runtime.EventHook;
import asruby.runtime.SharedScopeBlock;
import asruby.runtime.MethodIndex;
import asruby.runtime.ThreadContext;
import asruby.runtime.Visibility;
import asruby.runtime.builtin.IRubyObject;
import asruby.util.ByteList;
import asruby.runtime.Binding;
import asruby.runtime.InterpretedBlock;
import asruby.util.TypeConverter; */
import asruby.Ruby;
import asruby.lang.RubyArray;
import asruby.lang.RubyString;
import asruby.runtime.DynamicScope;
import asruby.runtime.IRubyObject;
import asruby.runtime.ThreadContext;

public class ASTInterpreter {
/*     public static function eval(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, block:Block):IRubyObject{
        //assert self != null : "self during eval must never be null";
        try {
            return evalInternal(runtime, context, node, self, block);
        } catch (sfe:StackOverflowError) {
            throw runtime.newSystemStackError("stack level too deep");
        }
    } */

    
    /**
     * Evaluate the given string under the specified binding object. If the binding is not a Proc or Binding object
     * (RubyProc or RubyBinding) throw an appropriate type error.
     * @param context TODO
     * @param evalString The string containing the text to be evaluated
     * @param binding The binding object under which to perform the evaluation
     * @param file The filename to use when reporting errors during the evaluation
     * @param lineNumber is the line number to pretend we are starting from
     * @return An IRubyObject result from the evaluation
     */
/*     public static function evalWithBinding(context:ThreadContext , src:IRubyObject, scope:IRubyObject , 
            file:String, lineNumber:int):IRubyObject {
        // both of these are ensured by the (very few) callers
        //assert !scope.isNil();
        //assert file != null;

        var runtime: Ruby =  src.getRuntime();
        var savedFile: String =  context.getFile();
        var savedLine: int =  context.getLine();

        if (!(scope instanceof RubyBinding)) {
            if (scope instanceof RubyProc) {
                scope = ((RubyProc) scope).binding();
            } else {
                // bomb out, it's not a binding or a proc
                throw runtime.newTypeError("wrong argument type " + scope.getMetaClass() + " (expected Proc/Binding)");
            }
        }

        var binding: Binding =  ((RubyBinding)scope).getBinding();
        var evalScope: DynamicScope =  binding.getDynamicScope().getEvalScope();

        // If no explicit file passed in we will use the bindings location
        if (file == null) file = binding.getFrame().getFile();
        if (lineNumber == -1) lineNumber = binding.getFrame().getLine();
        
        // FIXME:  This determine module is in a strange location and should somehow be in block
        evalScope.getStaticScope().determineModule();

        try {
            // Binding provided for scope, use it
            context.preEvalWithBinding(binding);
            var newSelf: IRubyObject =  binding.getSelf();
            var source: RubyString =  src.convertToString();
            var node: Node =  
                runtime.parseEval(source.getByteList(), file, evalScope, lineNumber);

            return eval(runtime, context, node, newSelf, binding.getFrame().getBlock());
        } catch (bj:JumpException.BreakJump) {
            throw runtime.newLocalJumpError("break", (IRubyObject)bj.getValue(), "unexpected break");
        } catch (rj:JumpException.RedoJump) {
            throw runtime.newLocalJumpError("redo", (IRubyObject)rj.getValue(), "unexpected redo");
        } finally {
            context.postEvalWithBinding(binding);

            // restore position
            context.setFile(savedFile);
            context.setLine(savedLine);
        }
    }
 */
    /**
     * Evaluate the given string.
     * @param context TODO
     * @param evalString The string containing the text to be evaluated
     * @param file The filename to use when reporting errors during the evaluation
     * @param lineNumber that the eval supposedly starts from
     * @return An IRubyObject result from the evaluation
     */
/* 	public static function evalSimple(context:ThreadContext, self:IRubyObject, src:IRubyObject, file:String, lineNumber:int):IRubyObject{
        // this is ensured by the callers
        //assert file != null;

        var runtime: Ruby =  src.getRuntime();
        var savedFile: String =  context.getFile();
        var savedLine: int =  context.getLine();

        // no binding, just eval in "current" frame (caller's frame)
        var source: RubyString =  src.convertToString();
        
        var evalScope: DynamicScope =  context.getCurrentScope().getEvalScope();
        evalScope.getStaticScope().determineModule();
        
        try {
            var node: Node =  runtime.parseEval(source.getByteList(), file, evalScope, lineNumber);
            
            return ASTInterpreter.eval(runtime, context, node, self, Block.NULL_BLOCK);
        } catch (bj:JumpException) {
        	context.setFile(savedFile);
            context.setLine(savedLine);
            throw runtime.newLocalJumpError("break", IRubyObject(bj).getValue(), "unexpected break");
        }
        return null;
    }
 */
 /*    private static function evalInternal(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        do {
            if (node == null) return nilNode(runtime, context);

            switch (node.nodeId) {
            case ALIASNODE:
                return aliasNode(runtime, context, node);
            case ANDNODE: {
                var iVisited: BinaryOperatorNode =  (BinaryOperatorNode) node;
   
                var result: IRubyObject =  evalInternal(runtime,context, iVisited.getFirstNode(), self, aBlock);
                if (!result.isTrue()) return result;
                node = iVisited.getSecondNode();
                continue;
            }
            case ARGSCATNODE:
                return argsCatNode(runtime, context, node, self, aBlock);
            case ARGSPUSHNODE:
                return argsPushNode(runtime, context, node, self, aBlock);
            case ARRAYNODE:
                return arrayNode(runtime, context, node, self, aBlock);
            case ATTRASSIGNNODE:
                return attrAssignNode(runtime, context, node, self, aBlock); 
            case BACKREFNODE:
                return backRefNode(context, node);
            case BEGINNODE: 
                node = ((BeginNode)node).getBodyNode();
                continue;
            case BIGNUMNODE:
                return bignumNode(runtime, node);
            case BLOCKNODE:
                return blockNode(runtime, context, node, self, aBlock);
            case BLOCKPASSNODE:
            assert false: "Call nodes and friends deal with this";
            case BREAKNODE:
                return breakNode(runtime, context, node, self, aBlock);
            case CALLNODE:
                return callNode(runtime, context, node, self, aBlock);
            case CASENODE:
                return caseNode(runtime, context, node, self, aBlock);
            case CLASSNODE:
                return classNode(runtime, context, node, self, aBlock);
            case CLASSVARASGNNODE:
                return classVarAsgnNode(runtime, context, node, self, aBlock);
            case CLASSVARDECLNODE:
                return classVarDeclNode(runtime, context, node, self, aBlock);
            case CLASSVARNODE:
                return classVarNode(runtime, context, node, self);
            case COLON2NODE:
                return colon2Node(runtime, context, node, self, aBlock);
            case COLON3NODE:
                return colon3Node(runtime, node);
            case CONSTDECLNODE:
                return constDeclNode(runtime, context, node, self, aBlock);
            case CONSTNODE:
                return constNode(context, node);
            case DASGNNODE:
                return dAsgnNode(runtime, context, node, self, aBlock);
            case DEFINEDNODE:
                return definedNode(runtime, context, node, self, aBlock);
            case DEFNNODE:
                return defnNode(runtime, context, node);
            case DEFSNODE:
                return defsNode(runtime, context, node, self, aBlock);
            case DOTNODE:
                return dotNode(runtime, context, node, self, aBlock);
            case DREGEXPNODE:
                return dregexpNode(runtime, context, node, self, aBlock);
            case DSTRNODE:
                return dStrNode(runtime, context, node, self, aBlock);
            case DSYMBOLNODE:
                return dSymbolNode(runtime, context, node, self, aBlock);
            case DVARNODE:
                return dVarNode(runtime, context, node);
            case DXSTRNODE:
                return dXStrNode(runtime, context, node, self, aBlock);
            case ENSURENODE:
                return ensureNode(runtime, context, node, self, aBlock);
            case EVSTRNODE:
                return evStrNode(runtime, context, node, self, aBlock);
            case FALSENODE:
                return falseNode(runtime, context);
            case FCALLNODE:
                return fCallNode(runtime, context, node, self, aBlock);
            case FIXNUMNODE:
                return fixnumNode(runtime, node);
            case FLIPNODE:
                return flipNode(runtime, context, node, self, aBlock);
            case FLOATNODE:
                return floatNode(runtime, node);
            case FORNODE:
                return forNode(runtime, context, node, self, aBlock);
            case GLOBALASGNNODE:
                return globalAsgnNode(runtime, context, node, self, aBlock);
            case GLOBALVARNODE:
                return globalVarNode(runtime, context, node);
            case HASHNODE:
                return hashNode(runtime, context, node, self, aBlock);
            case IFNODE: {
                var iVisited: IfNode =  (IfNode) node;
                var result: IRubyObject =  evalInternal(runtime,context, iVisited.getCondition(), self, aBlock);

                if (result.isTrue()) {
                    node = iVisited.getThenBody();
                } else {
                    node = iVisited.getElseBody();
                }
                continue;
            }
            case INSTASGNNODE:
                return instAsgnNode(runtime, context, node, self, aBlock);
            case INSTVARNODE:
                return instVarNode(runtime, node, self);
            case ITERNODE: 
            assert false: "Call nodes deal with these directly";
            case LOCALASGNNODE:
                return localAsgnNode(runtime, context, node, self, aBlock);
            case LOCALVARNODE:
                return localVarNode(runtime, context, node);
            case MATCH2NODE:
                return match2Node(runtime, context, node, self, aBlock);
            case MATCH3NODE:
                return match3Node(runtime, context, node, self, aBlock);
            case MATCHNODE:
                return matchNode(runtime, context, node, self, aBlock);
            case MODULENODE:
                return moduleNode(runtime, context, node, self, aBlock);
            case MULTIPLEASGNNODE:
                return multipleAsgnNode(runtime, context, node, self, aBlock);
            case NEWLINENODE: {
                var iVisited: NewlineNode =  (NewlineNode) node;
        
                // something in here is used to build up ruby stack trace...
                context.setFile(iVisited.getPosition().getFile());
                context.setLine(iVisited.getPosition().getStartLine());

                if (isTrace(runtime)) {
                    callTraceFunction(runtime, context, EventHook.RUBY_EVENT_LINE);
                }

                // TODO: do above but not below for additional newline nodes
                node = iVisited.getNextNode();
                continue;
            }
            case NEXTNODE:
                return nextNode(runtime, context, node, self, aBlock);
            case NILNODE:
                return nilNode(runtime, context);
            case NOTNODE:
                return notNode(runtime, context, node, self, aBlock);
            case NTHREFNODE:
                return nthRefNode(context, node);
            case OPASGNANDNODE: {
                var iVisited: BinaryOperatorNode =  (BinaryOperatorNode) node;
        
                // add in reverse order
                var result: IRubyObject =  evalInternal(runtime,context, iVisited.getFirstNode(), self, aBlock);
                if (!result.isTrue()) return pollAndReturn(context, result);
                node = iVisited.getSecondNode();
                continue;
            }
            case OPASGNNODE:
                return opAsgnNode(runtime, context, node, self, aBlock);
            case OPASGNORNODE:
                return opAsgnOrNode(runtime, context, node, self, aBlock);
            case OPELEMENTASGNNODE:
                return opElementAsgnNode(runtime, context, node, self, aBlock);
            case ORNODE:
                return orNode(runtime, context, node, self, aBlock);
            case PREEXENODE:
                return preExeNode(runtime, context, node, self, aBlock);
            case POSTEXENODE:
                return postExeNode(runtime, context, node, self, aBlock);
            case REDONODE: 
                return redoNode(context, node);
            case REGEXPNODE:
                return regexpNode(runtime, node);
            case RESCUEBODYNODE:
                node = ((RescueBodyNode)node).getBodyNode();
                continue;
            case RESCUENODE:
                return rescueNode(runtime, context, node, self, aBlock);
            case RETRYNODE:
                return retryNode(context);
            case RETURNNODE: 
                return returnNode(runtime, context, node, self, aBlock);
            case ROOTNODE:
                return rootNode(runtime, context, node, self, aBlock);
            case SCLASSNODE:
                return sClassNode(runtime, context, node, self, aBlock);
            case SELFNODE:
                return pollAndReturn(context, self);
            case SPLATNODE:
                return splatNode(runtime, context, node, self, aBlock);
            case STRNODE:
                return strNode(runtime, node);
            case SUPERNODE:
                return superNode(runtime, context, node, self, aBlock);
            case SVALUENODE:
                return sValueNode(runtime, context, node, self, aBlock);
            case SYMBOLNODE:
                return symbolNode(runtime, node);
            case TOARYNODE:
                return toAryNode(runtime, context, node, self, aBlock);
            case TRUENODE:
                return trueNode(runtime, context);
            case UNDEFNODE:
                return undefNode(runtime, context, node);
            case UNTILNODE:
                return untilNode(runtime, context, node, self, aBlock);
            case VALIASNODE:
                return valiasNode(runtime, node);
            case VCALLNODE:
                return vcallNode(runtime, context, node, self);
            case WHENNODE:
                var false:assert;
                return null;
            case WHILENODE:
                return whileNode(runtime, context, node, self, aBlock);
            case XSTRNODE:
                return xStrNode(runtime, context, node, self);
            case YIELDNODE:
                return yieldNode(runtime, context, node, self, aBlock);
            case ZARRAYNODE:
                return zArrayNode(runtime);
            case ZSUPERNODE:
                return zsuperNode(runtime, context, node, self, aBlock);
            default:
                throw new RuntimeException("Invalid node encountered in interpreter: \"" + node.getClass().getName() + "\", please report this at www.jruby.org");
            }
        } while(true);
    }

    private static function aliasNode(runtime:Ruby, context:ThreadContext, node:Node):IRubyObject{
        var iVisited: AliasNode =  (AliasNode) node;
        RuntimeHelpers.defineAlias(context, iVisited.getNewName(), iVisited.getOldName());
        var module: RubyModule =  context.getRubyClass();
   
        if (module == null) throw runtime.newTypeError("no class to make alias");
   
        module.defineAlias(iVisited.getNewName(), iVisited.getOldName());
        module.callMethod(context, "method_added", runtime.fastNewSymbol(iVisited.getNewName()));
   
        return runtime.getNil();
    }
    
    private static function argsCatNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: ArgsCatNode =  (ArgsCatNode) node;
   
        var args: IRubyObject =  evalInternal(runtime,context, iVisited.getFirstNode(), self, aBlock);
        var secondArgs: IRubyObject =  splatValue(runtime, evalInternal(runtime,context, iVisited.getSecondNode(), self, aBlock));
        var list: RubyArray =  args instanceof RubyArray ? (RubyArray) args : runtime.newArray(args);
   
        return list.concat(secondArgs);
    }

    private static function argsPushNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: ArgsPushNode =  (ArgsPushNode) node;
        
        var args: RubyArray =  (RubyArray) evalInternal(runtime,context, iVisited.getFirstNode(), self, aBlock).dup();
        return args.append(evalInternal(runtime,context, iVisited.getSecondNode(), self, aBlock));
    }

    private static function arrayNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: ArrayNode =  (ArrayNode) node;
        var array: Array =  new IRubyObject[iVisited.size()];
        
        for ( var int:i = 0; i < iVisited.size(); i++) {
            var next: Node =  iVisited.get(i);
   
            array[i] = evalInternal(runtime,context, next, self, aBlock);
        }
   
        if (iVisited.isLightweight()) {
            return runtime.newArrayNoCopyLight(array);
        }
        
        return runtime.newArrayNoCopy(array);
    }

    public static function arrayValue(runtime:Ruby, value:IRubyObject):RubyArray{
        var tmp: IRubyObject =  value.checkArrayType();

        if (tmp.isNil()) {
            // Object#to_a is obsolete.  We match Ruby's hack until to_a goes away.  Then we can 
            // remove this hack too.
            if (value.getMetaClass().searchMethod("to_a").getImplementationClass() != runtime.getKernel()) {
                value = value.callMethod(runtime.getCurrentContext(), MethodIndex.TO_A, "to_a");
                if (!(value instanceof RubyArray)) throw runtime.newTypeError("`to_a' did not return Array");
                return (RubyArray)value;
            } else {
                return runtime.newArray(value);
            }
        }
        return (RubyArray)tmp;
    }

    public static function aryToAry(runtime:Ruby, value:IRubyObject):IRubyObject{
        if (value instanceof RubyArray) return value;

        if (value.respondsTo("to_ary")) {
            return TypeConverter.convertToType(value, runtime.getArray(), MethodIndex.TO_A, "to_ary", false);
        }

        return runtime.newArray(value);
    }

    private static function attrAssignNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: AttrAssignNode =  (AttrAssignNode) node;
   
        var receiver: IRubyObject =  evalInternal(runtime,context, iVisited.getReceiverNode(), self, aBlock);
        var args: Array =  setupArgs(runtime, context, iVisited.getArgsNode(), self, aBlock);
        
        assert receiver.getMetaClass() != null : receiver.getClass().getName();
        
        // If reciever is self then we do the call the same way as vcall
        var callType: CallType =  (receiver == self ? CallType.VARIABLE : CallType.NORMAL);
   
        var module: RubyModule =  receiver.getMetaClass();
        
        var name: String =  iVisited.getName();

        var method: DynamicMethod =  module.searchMethod(name);

        if (method.isUndefined() || (!method.isCallableFrom(self, callType))) {
            RuntimeHelpers.callMethodMissing(context, receiver, method, name, args, self, callType, Block.NULL_BLOCK);
        } else {
            method.call(context, receiver, module, name, args);
        }

        return args[args.length - 1];
    }

    private static function backRefNode(context:ThreadContext, node:Node):IRubyObject{
        var iVisited: BackRefNode =  (BackRefNode) node;
        var backref: IRubyObject =  context.getCurrentFrame().getBackRef();
        switch (iVisited.getType()) {
        case '&':
            return RubyRegexp.last_match(backref);
        case '`':
            return RubyRegexp.match_pre(backref);
        case '\'':
            return RubyRegexp.match_post(backref);
        case '+':
            return RubyRegexp.match_last(backref);
        default:
            assert false: "backref with invalid type";
            return null;
        }
    }

    private static function bignumNode(runtime:Ruby, node:Node):IRubyObject{
        return RubyBignum.newBignum(runtime, ((BignumNode)node).getValue());
    }

    private static function blockNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: BlockNode =  (BlockNode) node;
   
        var result: IRubyObject =  runtime.getNil();
        for ( var int:i = 0; i < iVisited.size(); i++) {
            result = evalInternal(runtime,context, iVisited.get(i), self, aBlock);
        }
   
        return result;
    }

    private static function breakNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: BreakNode =  (BreakNode) node;
   
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
   
        throw new JumpException.BreakJump(null, result);
    }

    private static function callNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: CallNode =  (CallNode) node;

        var receiver: IRubyObject =  evalInternal(runtime,context, iVisited.getReceiverNode(), self, aBlock);
        var args: Array =  setupArgs(runtime, context, iVisited.getArgsNode(), self, aBlock);
        
        assert receiver.getMetaClass() != null : receiver.getClass().getName();

        var block: Block =  getBlock(runtime, context, self, aBlock, iVisited.getIterNode());

        // No block provided lets look at fast path for STI dispatch.
        if (!block.isGiven()) {
            return iVisited.callAdapter.call(context, receiver, args);
        }
            
        while (true) {
            try {
                return iVisited.callAdapter.call(context, receiver, args, block);
            } catch (rj:JumpException.RetryJump) {
                // allow loop to retry
            } catch (bj:JumpException.BreakJump) {
                return (IRubyObject) bj.getValue();
            }
        }
    }

    private static function caseNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: CaseNode =  (CaseNode) node;
        var expression: IRubyObject =  null;
        if (iVisited.getCaseNode() != null) {
            expression = evalInternal(runtime,context, iVisited.getCaseNode(), self, aBlock);
        }

        context.pollThreadEvents();

        var result: IRubyObject =  runtime.getNil();

        var firstWhenNode: Node =  iVisited.getFirstWhenNode();
        while (firstWhenNode != null) {
            if (!(firstWhenNode instanceof WhenNode)) {
                node = firstWhenNode;
                return evalInternal(runtime, context, node, self, aBlock);
            }

            var whenNode: WhenNode =  (WhenNode) firstWhenNode;

            if (whenNode.getExpressionNodes() instanceof ArrayNode) {
                var arrayNode: ArrayNode =  (ArrayNode)whenNode.getExpressionNodes();
                // All expressions in a while are in same file
                context.setFile(arrayNode.getPosition().getFile());
                for ( var int:i = 0; i < arrayNode.size(); i++) {
                    var tag: Node =  arrayNode.get(i);

                    context.setLine(tag.getPosition().getStartLine());
                    
                    if (isTrace(runtime)) {
                        callTraceFunction(runtime, context, EventHook.RUBY_EVENT_LINE);
                    }

                    // Ruby grammar has nested whens in a case body because of
                    // productions case_body and when_args.
                    if (tag instanceof WhenNode) {
                        var expressionsObject: IRubyObject =  evalInternal(runtime,context, ((WhenNode) tag)
                                        .getExpressionNodes(), self, aBlock);
                        var expressions: RubyArray =  splatValue(runtime, expressionsObject);

                        for ( var int:j = 0,k = expressions.getLength(); j < k; j++) {
                            var condition: IRubyObject =  expressions.eltInternal(j);

                            if ((expression != null && condition.callMethod(context, MethodIndex.OP_EQQ, "===", expression)
                                    .isTrue())
                                    || (expression == null && condition.isTrue())) {
                                node = ((WhenNode) firstWhenNode).getBodyNode();
                                return evalInternal(runtime, context, node, self, aBlock);
                            }
                        }
                        continue;
                    }

                    result = evalInternal(runtime,context, tag, self, aBlock);

                    if ((expression != null && result.callMethod(context, MethodIndex.OP_EQQ, "===", expression).isTrue())
                            || (expression == null && result.isTrue())) {
                        node = whenNode.getBodyNode();
                        return evalInternal(runtime, context, node, self, aBlock);
                    }
                }
            } else {
                result = evalInternal(runtime,context, whenNode.getExpressionNodes(), self, aBlock);

                if ((expression != null && result.callMethod(context, MethodIndex.OP_EQQ, "===", expression).isTrue())
                        || (expression == null && result.isTrue())) {
                    node = ((WhenNode) firstWhenNode).getBodyNode();
                    return evalInternal(runtime, context, node, self, aBlock);
                }
            }

            context.pollThreadEvents();

            firstWhenNode = whenNode.getNextCase();
        }

        return runtime.getNil();
    }

    private static function classNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: ClassNode =  (ClassNode) node;
        var classNameNode: Node =  iVisited.getCPath();

        var enclosingClass: RubyModule =  getEnclosingModule(runtime, context, classNameNode, self, aBlock);

        if (enclosingClass == null) throw runtime.newTypeError("no outer class/module");

        var superNode: Node =  iVisited.getSuperNode();

        var superClass: RubyClass =  null;

        if (superNode != null) {
            var superObj: IRubyObject =  evalInternal(runtime, context, superNode, self, aBlock);
            RubyClass.checkInheritable(superObj);
            superClass = (RubyClass)superObj;
        }

        var name: String =  ((INameNode) classNameNode).getName();        

        var clazz: RubyClass =  enclosingClass.defineOrGetClassUnder(name, superClass);

        var scope: StaticScope =  iVisited.getScope();
        scope.setModule(clazz);

        return evalClassDefinitionBody(runtime, context, scope, iVisited.getBodyNode(), clazz, self, aBlock);
    }

    private static function classVarAsgnNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: ClassVarAsgnNode =  (ClassVarAsgnNode) node;
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
        var rubyClass: RubyModule =  getClassVariableBase(context, runtime);
   
        if (rubyClass == null) rubyClass = self.getMetaClass();

        rubyClass.fastSetClassVar(iVisited.getName(), result);
   
        return result;
    }

    private static function classVarDeclNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: ClassVarDeclNode =  (ClassVarDeclNode) node;
        var rubyClass: RubyModule =  getClassVariableBase(context, runtime);
        
        if (rubyClass == null) {
            throw runtime.newTypeError("no class/module to define class variable");
        }
        
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
        rubyClass.fastSetClassVar(iVisited.getName(), result);
   
        return result;
    }

    private static function classVarNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject):IRubyObject{
        var iVisited: ClassVarNode =  (ClassVarNode) node;
        var rubyClass: RubyModule =  getClassVariableBase(context, runtime);
   
        if (rubyClass == null) rubyClass = self.getMetaClass();

        return rubyClass.getClassVar(iVisited.getName());
    }

    private static function colon2Node(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: Colon2Node =  (Colon2Node) node;
        var leftNode: Node =  iVisited.getLeftNode();
        // TODO: Made this more colon3 friendly because of cpath production
        // rule in grammar (it is convenient to think of them as the same thing
        // at a grammar level even though evaluation is).
        if (leftNode == null) {
            return runtime.getObject().fastGetConstantFrom(iVisited.getName());
        } else {
            var result: IRubyObject =  evalInternal(runtime,context, iVisited.getLeftNode(), self, aBlock);
            if (result instanceof RubyModule) {
                return ((RubyModule) result).fastGetConstantFrom(iVisited.getName());
            } else {
                return result.callMethod(context, iVisited.getName(), IRubyObject.NULL_ARRAY, aBlock);
            }
        }
    }

    private static function colon3Node(runtime:Ruby, node:Node):IRubyObject{
        return runtime.getObject().fastGetConstantFrom(((Colon3Node)node).getName());
    }

    private static function constDeclNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: ConstDeclNode =  (ConstDeclNode) node;
        var constNode: Node =  iVisited.getConstNode();
   
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
        
        if (constNode == null) {
            return context.setConstantInCurrent(iVisited.getName(), result);
        } else if (constNode.nodeId == NodeType.COLON2NODE) {
            var obj: IRubyObject =  evalInternal(runtime,context, ((Colon2Node) iVisited.getConstNode()).getLeftNode(), self, aBlock);
            return context.setConstantInModule(iVisited.getName(), obj, result);
        } else { // colon3
            return context.setConstantInObject(iVisited.getName(), result);
        }
    }

    private static function constNode(context:ThreadContext, node:Node):IRubyObject{
        return context.getConstant(((ConstNode)node).getName());
    }

    private static function dAsgnNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: DAsgnNode =  (DAsgnNode) node;
   
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);

        // System.out.println("DSetting: " + iVisited.getName() + " at index " + iVisited.getIndex() + " and at depth " + iVisited.getDepth() + " and set " + result);
        context.getCurrentScope().setValue(iVisited.getIndex(), result, iVisited.getDepth());
   
        return result;
    }

    private static function definedNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: DefinedNode =  (DefinedNode) node;
        var definition: String =  getDefinition(runtime, context, iVisited.getExpressionNode(), self, aBlock);
        if (definition != null) {
            return runtime.newString(definition);
        } else {
            return runtime.getNil();
        }
    }

    private static function defnNode(runtime:Ruby, context:ThreadContext, node:Node):IRubyObject{
        var iVisited: DefnNode =  (DefnNode) node;
        
        var containingClass: RubyModule =  context.getRubyClass();
   
        if (containingClass == null) {
            throw runtime.newTypeError("No class to add method.");
        }
   
        var name: String =  iVisited.getName();

        if (containingClass == runtime.getObject() && name == "initialize") {
            runtime.getWarnings().warn(ID.REDEFINING_DANGEROUS, "redefining Object#initialize may cause infinite loop", "Object#initialize");
        }

        if (name == "__id__" || name == "__send__") {
            runtime.getWarnings().warn(ID.REDEFINING_DANGEROUS, "redefining `" + name + "' may cause serious problem", name); 
        }

        var visibility: Visibility =  context.getCurrentVisibility();
        if (name == "initialize" || visibility == Visibility.MODULE_FUNCTION) {
            visibility = Visibility.PRIVATE;
        }
        
        var scope: StaticScope =  iVisited.getScope();
        scope.determineModule();
        
        var newMethod: DefaultMethod =  new DefaultMethod(containingClass, scope, 
                iVisited.getBodyNode(), (ArgsNode) iVisited.getArgsNode(), 
                visibility, iVisited.getPosition());
   
        containingClass.addMethod(name, newMethod);
   
        if (context.getCurrentVisibility() == Visibility.MODULE_FUNCTION) {
            containingClass.getSingletonClass().addMethod(
                    name,
                    new WrapperMethod(containingClass.getSingletonClass(), newMethod,
                            Visibility.PUBLIC));
            containingClass.callMethod(context, "singleton_method_added", runtime.fastNewSymbol(name));
        }
   
        // 'class << state.self' and 'class << obj' uses defn as opposed to defs
        if (containingClass.isSingleton()) {
            ((MetaClass) containingClass).getAttached().callMethod(
                    context, "singleton_method_added", runtime.fastNewSymbol(iVisited.getName()));
        } else {
            containingClass.callMethod(context, "method_added", runtime.fastNewSymbol(name));
        }
   
        return runtime.getNil();
    }
    
    private static function defsNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: DefsNode =  (DefsNode) node;
        var receiver: IRubyObject =  evalInternal(runtime,context, iVisited.getReceiverNode(), self, aBlock);
        var name: String =  iVisited.getName();

        if (runtime.getSafeLevel() >= 4 && !receiver.isTaint()) {
            throw runtime.newSecurityError("Insecure; can't define singleton method.");
        }

        if (receiver instanceof RubyFixnum || receiver instanceof RubySymbol) {
          throw runtime.newTypeError("can't define singleton method \"" + name
          + "\" for " + receiver.getMetaClass().getBaseName());
        }

        if (receiver.isFrozen()) throw runtime.newFrozenError("object");

        var rubyClass: RubyClass =  receiver.getSingletonClass();

        if (runtime.getSafeLevel() >= 4 && rubyClass.getMethods().get(name) != null) {
            throw runtime.newSecurityError("redefining method prohibited.");
        }

        var scope: StaticScope =  iVisited.getScope();
        scope.determineModule();
      
        var newMethod: DefaultMethod =  new DefaultMethod(rubyClass, scope, iVisited.getBodyNode(), 
                (ArgsNode) iVisited.getArgsNode(), Visibility.PUBLIC, iVisited.getPosition());
   
        rubyClass.addMethod(name, newMethod);
        receiver.callMethod(context, "singleton_method_added", runtime.fastNewSymbol(name));
   
        return runtime.getNil();
    }

    private static function dotNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: DotNode =  (DotNode) node;
        return RubyRange.newRange(runtime, context,
                evalInternal(runtime,context, iVisited.getBeginNode(), self, aBlock), 
                evalInternal(runtime,context, iVisited.getEndNode(), self, aBlock), 
                iVisited.isExclusive());
    }

    private static function dregexpNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: DRegexpNode =  (DRegexpNode) node;
        
        var regexp:RubyRegexp;
        if (iVisited.getOnce()) {
            regexp = iVisited.getOnceRegexp();
            if (regexp != null) {
                return regexp;
            }
        }

        var string: RubyString =  runtime.newString(new ByteList());
        for ( var int:i = 0; i < iVisited.size(); i++) {
            var iterNode: Node =  iVisited.get(i);
            if (iterNode instanceof StrNode) {
                string.getByteList().append(((StrNode) iterNode).getValue());
            } else {
                string.append(evalInternal(runtime,context, iterNode, self, aBlock));
            }
        }
   
        try {
            regexp = RubyRegexp.newRegexp(runtime, string.getByteList(), iVisited.getOptions());
        } catch (e:Exception) {
        //                    System.err.println(iVisited.getValue().toString());
        //                    e.printStackTrace();
            throw runtime.newRegexpError(e.getMessage());
        }
        
        if (iVisited.getOnce()) {
            iVisited.setOnceRegexp(regexp);
        }

        return regexp;
    }
    
    private static function dStrNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: DStrNode =  (DStrNode) node;
   
        var string: RubyString =  runtime.newString(new ByteList());
        for ( var int:i = 0; i < iVisited.size(); i++) {
            var iterNode: Node =  iVisited.get(i);
            if (iterNode instanceof StrNode) {
                string.getByteList().append(((StrNode) iterNode).getValue());
            } else {
                string.append(evalInternal(runtime,context, iterNode, self, aBlock));
            }
        }
   
        return string;
    }

    private static function dSymbolNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: DSymbolNode =  (DSymbolNode) node;
   
        var string: RubyString =  runtime.newString(new ByteList());
        for ( var int:i = 0; i < iVisited.size(); i++) {
            var iterNode: Node =  iVisited.get(i);
            if (iterNode instanceof StrNode) {
                string.getByteList().append(((StrNode) iterNode).getValue());
            } else {
                string.append(evalInternal(runtime,context, iterNode, self, aBlock));
            }
        }
   
        return runtime.newSymbol(string.toString());
    }

    private static function dVarNode(runtime:Ruby, context:ThreadContext, node:Node):IRubyObject{
        var iVisited: DVarNode =  (DVarNode) node;

        // System.out.println("DGetting: " + iVisited.getName() + " at index " + iVisited.getIndex() + " and at depth " + iVisited.getDepth());
        var obj: IRubyObject =  context.getCurrentScope().getValue(iVisited.getIndex(), iVisited.getDepth());

        // FIXME: null check is removable once we figure out how to assign to unset named block args
        return obj == null ? runtime.getNil() : obj;
    }

    private static function dXStrNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: DXStrNode =  (DXStrNode) node;
   
        var string: RubyString =  runtime.newString(new ByteList());
        for ( var int:i = 0; i < iVisited.size(); i++) {
            var iterNode: Node =  iVisited.get(i);
            if (iterNode instanceof StrNode) {
                string.getByteList().append(((StrNode) iterNode).getValue());
            } else {
                string.append(evalInternal(runtime,context, iterNode, self, aBlock));
            }
        }
   
        return self.callMethod(context, "`", string);
    }

    private static function ensureNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: EnsureNode =  (EnsureNode) node;
        
        // save entering the try if there's nothing to ensure
        if (iVisited.getEnsureNode() != null) {
            var result: IRubyObject =  runtime.getNil();

            try {
                result = evalInternal(runtime,context, iVisited.getBodyNode(), self, aBlock);
            } finally {
                evalInternal(runtime,context, iVisited.getEnsureNode(), self, aBlock);
            }

            return result;
        }

        node = iVisited.getBodyNode();
        return evalInternal(runtime, context, node, self, aBlock);
    }

    private static function evStrNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        return evalInternal(runtime,context, ((EvStrNode)node).getBody(), self, aBlock).asString();
    }
    
    private static function falseNode(runtime:Ruby, context:ThreadContext):IRubyObject{
        return pollAndReturn(context, runtime.getFalse());
    }

    private static function fCallNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: FCallNode =  (FCallNode) node;
        
        var args: Array =  setupArgs(runtime, context, iVisited.getArgsNode(), self, aBlock);
        var block: Block =  getBlock(runtime, context, self, aBlock, iVisited.getIterNode());

        // No block provided lets look at fast path for STI dispatch.
        if (!block.isGiven()) {
            return iVisited.callAdapter.call(context, self, args);
        }

        while (true) {
            try {
                return iVisited.callAdapter.call(context, self, args, block);
            } catch (rj:JumpException.RetryJump) {
                // allow loop to retry
            }
        }
    }

    private static function fixnumNode(runtime:Ruby, node:Node):IRubyObject{
        return ((FixnumNode)node).getFixnum(runtime);
    }

    private static function flipNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: FlipNode =  (FlipNode) node;
        var scope: DynamicScope =  context.getCurrentScope();

        // Make sure the appropriate scope has proper size. See JRUBY-2046.
        var nthParent: DynamicScope =  scope.getNthParentScope(iVisited.getDepth());
        if (nthParent != null) {
            nthParent.growIfNeeded();
        }

        var result: IRubyObject =  scope.getValue(iVisited.getIndex(), iVisited.getDepth());
   
        if (iVisited.isExclusive()) {
            if (result == null || !result.isTrue()) {
                result = evalInternal(runtime, context, iVisited.getBeginNode(), self, aBlock).isTrue() ? runtime.getTrue() : runtime.getFalse();
                scope.setValue(iVisited.getIndex(), result, iVisited.getDepth());
                return result;
            } else {
                if (evalInternal(runtime, context, iVisited.getEndNode(), self, aBlock).isTrue()) {
                    scope.setValue(iVisited.getIndex(), runtime.getFalse(), iVisited.getDepth());
                }
                
                return runtime.getTrue();
            }
        } else {
            if (result == null || !result.isTrue()) {
                if (evalInternal(runtime, context, iVisited.getBeginNode(), self, aBlock).isTrue()) {
                    scope.setValue(iVisited.getIndex(),
                            evalInternal(runtime, context, iVisited.getEndNode(), self, aBlock).isTrue() ? 
                                    runtime.getFalse() : runtime.getTrue(), iVisited.getDepth());
                    return runtime.getTrue();
                } 

                return runtime.getFalse();
            } else {
                if (evalInternal(runtime, context, iVisited.getEndNode(), self, aBlock).isTrue()) {
                    scope.setValue(iVisited.getIndex(), runtime.getFalse(), iVisited.getDepth());
                }
                return runtime.getTrue();
            }
        }
    }

    private static function floatNode(runtime:Ruby, node:Node):IRubyObject{
        return RubyFloat.newFloat(runtime, ((FloatNode)node).getValue());
    }

    private static function forNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: ForNode =  (ForNode) node;
        
        var block: Block =  SharedScopeBlock.newInterpretedSharedScopeClosure(context, iVisited, 
                context.getCurrentScope(), self);
   
        try {
            while (true) {
                try {
                    var savedFile: String =  context.getFile();
                    var savedLine: int =  context.getLine();
   
                    var recv: IRubyObject =  null;
                    try {
                        recv = evalInternal(runtime,context, iVisited.getIterNode(), self, aBlock);
                    } finally {
                        context.setFile(savedFile);
                        context.setLine(savedLine);
                    }
   
                    return iVisited.callAdapter.call(context, recv, block);
                } catch (rj:JumpException.RetryJump) {
                    // do nothing, allow loop to retry
                }
            }
        } catch (bj:JumpException.BreakJump) {
            return (IRubyObject) bj.getValue();
        }
    }

    private static function globalAsgnNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: GlobalAsgnNode =  (GlobalAsgnNode) node;
   
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
   
        runtime.getGlobalVariables().set(iVisited.getName(), result);
   
        return result;
    }

    private static function globalVarNode(runtime:Ruby, context:ThreadContext, node:Node):IRubyObject{
        var iVisited: GlobalVarNode =  (GlobalVarNode) node;
        
        return runtime.getGlobalVariables().get(iVisited.getName());
    }

    private static function hashNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: HashNode =  (HashNode) node;
   
        var hash: RubyHash =  null;
        if (iVisited.getListNode() != null) {
            hash = RubyHash.newHash(runtime);
   
        for ( var int:i = 0; i < iVisited.getListNode().size();) {
                // insert all nodes in sequence, hash them in the instruction
                // KEY
                var key: IRubyObject =  evalInternal(runtime,context, iVisited.getListNode().get(i++), self, aBlock);
                var value: IRubyObject =  evalInternal(runtime,context, iVisited.getListNode().get(i++), self, aBlock);
   
                hash.fastASet(key, value);
            }
        }
   
        if (hash == null) {
            return RubyHash.newHash(runtime);
        }
   
        return hash;
    }

    private static function instAsgnNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: InstAsgnNode =  (InstAsgnNode) node;
   
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
        self.getInstanceVariables().fastSetInstanceVariable(iVisited.getName(), result);
   
        return result;
    }

    private static function instVarNode(runtime:Ruby, node:Node, self:IRubyObject):IRubyObject{
        var iVisited: InstVarNode =  (InstVarNode) node;
        var variable: IRubyObject =  self.getInstanceVariables().fastGetInstanceVariable(iVisited.getName());
   
        if (variable != null) return variable;
        
        runtime.getWarnings().warning(ID.IVAR_NOT_INITIALIZED, iVisited.getPosition(), 
                "instance variable " + iVisited.getName() + " not initialized", iVisited.getName());
        
        return runtime.getNil();
    }

    private static function localAsgnNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: LocalAsgnNode =  (LocalAsgnNode) node;
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
        
        //System.out.println("LSetting: " + iVisited.getName() + " at index " + iVisited.getIndex() + " and at depth " + iVisited.getDepth() + " and set " + result);
        context.getCurrentScope().setValue(iVisited.getIndex(), result, iVisited.getDepth());

        return result;
    }

    private static function localVarNode(runtime:Ruby, context:ThreadContext, node:Node):IRubyObject{
        var iVisited: LocalVarNode =  (LocalVarNode) node;

        //        System.out.println("DGetting: " + iVisited.getName() + " at index " + iVisited.getIndex() + " and at depth " + iVisited.getDepth());
        var result: IRubyObject =  context.getCurrentScope().getValue(iVisited.getIndex(), iVisited.getDepth());

        return result == null ? runtime.getNil() : result;
    }

    private static function match2Node(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: Match2Node =  (Match2Node) node;
        var recv: IRubyObject =  evalInternal(runtime,context, iVisited.getReceiverNode(), self, aBlock);
        var value: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
   
        return ((RubyRegexp) recv).op_match(context, value);
    }
    
    private static function match3Node(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: Match3Node =  (Match3Node) node;
        var recv: IRubyObject =  evalInternal(runtime,context, iVisited.getReceiverNode(), self, aBlock);
        var value: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
   
        if (value instanceof RubyString) {
            return ((RubyRegexp) recv).op_match(context, value);
        } else {
            return iVisited.callAdapter.call(context, value, recv);
        }
    }

    private static function matchNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        return ((RubyRegexp) evalInternal(runtime,context, ((MatchNode)node).getRegexpNode(), self, aBlock)).op_match2(context);
    }

    private static function moduleNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: ModuleNode =  (ModuleNode) node;
        var classNameNode: Node =  iVisited.getCPath();

        var enclosingModule: RubyModule =  getEnclosingModule(runtime, context, classNameNode, self, aBlock);

        if (enclosingModule == null) throw runtime.newTypeError("no outer class/module");

        var name: String =  ((INameNode) classNameNode).getName();        

        var module: RubyModule =  enclosingModule.defineOrGetModuleUnder(name);

        var scope: StaticScope =  iVisited.getScope();
        scope.setModule(module);        

        return evalClassDefinitionBody(runtime, context, scope, iVisited.getBodyNode(), module, self, aBlock);
    }

    private static function multipleAsgnNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: MultipleAsgnNode =  (MultipleAsgnNode) node;
        
        switch (iVisited.getValueNode().nodeId) {
        case ARRAYNODE: {
            var iVisited2: ArrayNode =  (ArrayNode) iVisited.getValueNode();
            return multipleAsgnArrayNode(runtime, context, iVisited, iVisited2, self, aBlock);
        }
        case SPLATNODE: {
            var splatNode: SplatNode =  (SplatNode)iVisited.getValueNode();
            var rubyArray: RubyArray =  splatValue(runtime, evalInternal(runtime, context, splatNode.getValue(), self, aBlock));
            return AssignmentVisitor.multiAssign(runtime, context, self, iVisited, rubyArray, false);
        }
        default:
            var value: IRubyObject =  evalInternal(runtime, context, iVisited.getValueNode(), self, aBlock);

            if (!(value instanceof RubyArray)) {
                value = RubyArray.newArray(runtime, value);
            }
            
            return AssignmentVisitor.multiAssign(runtime, context, self, iVisited, (RubyArray)value, false);
        }
    }

    private static function multipleAsgnArrayNode(runtime:Ruby, context:ThreadContext, iVisited:MultipleAsgnNode, node:ArrayNode, self:IRubyObject, aBlock:Block):IRubyObject{
        var array: Array =  new IRubyObject[node.size()];

        for ( var int:i = 0; i < node.size(); i++) {
            var next: Node =  node.get(i);

            array[i] = evalInternal(runtime,context, next, self, aBlock);
        }
        return AssignmentVisitor.multiAssign(runtime, context, self, iVisited, RubyArray.newArrayNoCopyLight(runtime, array), false);
    }

    private static function nextNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: NextNode =  (NextNode) node;
   
        context.pollThreadEvents();
   
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
   
        // now used as an interpreter event
        throw new JumpException.NextJump(result);
    }

    private static function nilNode(runtime:Ruby, context:ThreadContext):IRubyObject{
        return pollAndReturn(context, runtime.getNil());
    }

    private static function notNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: NotNode =  (NotNode) node;
   
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getConditionNode(), self, aBlock);
        return result.isTrue() ? runtime.getFalse() : runtime.getTrue();
    }

    private static function nthRefNode(context:ThreadContext, node:Node):IRubyObject{
        return RubyRegexp.nth_match(((NthRefNode)node).getMatchNumber(), context.getCurrentFrame().getBackRef());
    }
    
    public static function pollAndReturn(context:ThreadContext, result:IRubyObject):IRubyObject{
        context.pollThreadEvents();
        return result;
    }

    private static function opAsgnNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: OpAsgnNode =  (OpAsgnNode) node;
        var receiver: IRubyObject =  evalInternal(runtime,context, iVisited.getReceiverNode(), self, aBlock);
        var value: IRubyObject =  iVisited.variableCallAdapter.call(context, receiver);
   
        if (iVisited.getOperatorName() == "||") {
            if (value.isTrue()) {
                return pollAndReturn(context, value);
            }
            value = evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
        } else if (iVisited.getOperatorName() == "&&") {
            if (!value.isTrue()) {
                return pollAndReturn(context, value);
            }
            value = evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
        } else {
            value = iVisited.operatorCallAdapter.call(context, value, 
                    evalInternal(runtime, context, iVisited.getValueNode(), self, aBlock));
        }
   
        iVisited.variableAsgnCallAdapter.call(context, receiver, value);
   
        return pollAndReturn(context, value);
    }

    private static function opAsgnOrNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: OpAsgnOrNode =  (OpAsgnOrNode) node;
        var def: String =  getDefinition(runtime, context, iVisited.getFirstNode(), self, aBlock);
   
        var result: IRubyObject =  runtime.getNil();
        if (def != null) {
            result = evalInternal(runtime,context, iVisited.getFirstNode(), self, aBlock);
        }
        if (!result.isTrue()) {
            result = evalInternal(runtime,context, iVisited.getSecondNode(), self, aBlock);
        }
   
        return pollAndReturn(context, result);
    }

    private static function opElementAsgnNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: OpElementAsgnNode =  (OpElementAsgnNode) node;
        var receiver: IRubyObject =  evalInternal(runtime,context, iVisited.getReceiverNode(), self, aBlock);
   
        var args: Array =  setupArgs(runtime, context, iVisited.getArgsNode(), self, aBlock);
   
        var firstValue: IRubyObject =  iVisited.elementAdapter.call(context, receiver, args);
        
        if (iVisited.getOperatorName() == "||") {
            if (firstValue.isTrue()) {
                return firstValue;
            }
            firstValue = evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
        } else if (iVisited.getOperatorName() == "&&") {
            if (!firstValue.isTrue()) {
                return firstValue;
            }
            firstValue = evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
        } else {
            firstValue = iVisited.callAdapter.call(context, firstValue, 
                    evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock));
        }
   
        var expandedArgs: Array =  new IRubyObject[args.length + 1];
        System.arraycopy(args, 0, expandedArgs, 0, args.length);
        expandedArgs[expandedArgs.length - 1] = firstValue;
        iVisited.elementAsgnAdapter.call(context, receiver, expandedArgs);
        
        return firstValue;
    }

    private static function orNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: OrNode =  (OrNode) node;
   
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getFirstNode(), self, aBlock);
   
        if (!result.isTrue()) {
            result = evalInternal(runtime,context, iVisited.getSecondNode(), self, aBlock);
        }
   
        return result;
    }

    private static function postExeNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: PostExeNode =  (PostExeNode) node;
        
        var block: Block =  SharedScopeBlock.newInterpretedSharedScopeClosure(context, iVisited, context.getCurrentScope(), self);
        
        runtime.pushExitBlock(runtime.newProc(Block.Type.LAMBDA, block));
        
        return runtime.getNil();
    }

    private static function preExeNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: PreExeNode =  (PreExeNode) node;
        
        var scope: DynamicScope =  DynamicScope.newDynamicScope(iVisited.getScope());
        // Each root node has a top-level scope that we need to push
        context.preScopedBody(scope);

        // FIXME: I use a for block to implement END node because we need a proc which captures
        // its enclosing scope.   ForBlock now represents these node and should be renamed.
        var block: Block =  InterpretedBlock.newInterpretedClosure(context, iVisited, self);
        
        block.yield(context, null);
        
        context.postScopedBody();

        return runtime.getNil();
    }

    private static function redoNode(context:ThreadContext, node:Node):IRubyObject{
        context.pollThreadEvents();
   
        // now used as an interpreter event
        throw JumpException.REDO_JUMP;
    }

    private static function regexpNode(runtime:Ruby, node:Node):IRubyObject{
        var iVisited: RegexpNode =  (RegexpNode) node;
        var p: RubyRegexp =  iVisited.getPattern();
        if(p == null) {
            p = RubyRegexp.newRegexp(runtime, iVisited.getValue(), iVisited.getOptions());
            iVisited.setPattern(p);
        }

        return p;
    }

    private static function rescueNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: RescueNode =  (RescueNode)node;
        RescuedBlock : while (true) {
            var globalExceptionState: IRubyObject =  runtime.getGlobalVariables().get("$!");
            var anotherExceptionRaised: Boolean =  false;
            try {
                // Execute rescue block
                var result: IRubyObject =  evalInternal(runtime,context, iVisited.getBodyNode(), self, aBlock);

                // If no exception is thrown execute else block
                if (iVisited.getElseNode() != null) {
                    if (iVisited.getRescueNode() == null) {
                        runtime.getWarnings().warn(ID.ELSE_WITHOUT_RESCUE, iVisited.getElseNode().getPosition(), "else without rescue is useless");
                    }
                    result = evalInternal(runtime,context, iVisited.getElseNode(), self, aBlock);
                }

                return result;
            } catch (raiseJump:RaiseException) {
                var raisedException: RubyException =  raiseJump.getException();
                // TODO: Rubicon TestKernel dies without this line.  A cursory glance implies we
                // falsely set $! to nil and this sets it back to something valid.  This should 
                // get fixed at the same time we address bug #1296484.
                runtime.getGlobalVariables().set("$!", raisedException);

                var rescueNode: RescueBodyNode =  iVisited.getRescueNode();

                while (rescueNode != null) {
                    var exceptionNodes: Node =  rescueNode.getExceptionNodes();
                    var exceptionNodesList:ListNode;
                    
                    if (exceptionNodes instanceof SplatNode) {                    
                        exceptionNodesList = (ListNode) evalInternal(runtime,context, exceptionNodes, self, aBlock);
                    } else {
                        exceptionNodesList = (ListNode) exceptionNodes;
                    }

                    var exceptions:Array;
                    if (exceptionNodesList == null) {
                        exceptions = new IRubyObject[] {runtime.fastGetClass("StandardError")};
                    } else {
                        exceptions = setupArgs(runtime, context, exceptionNodes, self, aBlock);
                    }
                    if (RuntimeHelpers.isExceptionHandled(raisedException, exceptions, runtime, context, self).isTrue()) {
                        try {
                            return evalInternal(runtime,context, rescueNode, self, aBlock);
                        } catch (rj:JumpException.RetryJump) {
                            // should be handled in the finally block below
                            //state.runtime.getGlobalVariables().set("$!", state.runtime.getNil());
                            //state.threadContext.setRaisedException(null);
                            var RescuedBlock:continue;
                        } catch (je:RaiseException) {
                            anotherExceptionRaised = true;
                            throw je;
                        }
                    }
                    
                    rescueNode = rescueNode.getOptRescueNode();
                }

                // no takers; bubble up
                throw raiseJump;
            } finally {
                // clear exception when handled or retried
                if (!anotherExceptionRaised)
                    runtime.getGlobalVariables().set("$!", globalExceptionState);
            }
        }
    }

    private static function retryNode(context:ThreadContext):IRubyObject{
        context.pollThreadEvents();
   
        throw JumpException.RETRY_JUMP;
    }
    
    private static function returnNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: ReturnNode =  (ReturnNode) node;
   
        var result: IRubyObject =  evalInternal(runtime,context, iVisited.getValueNode(), self, aBlock);
   
        throw new JumpException.ReturnJump(context.getFrameJumpTarget(), result);
    }

    private static function rootNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: RootNode =  (RootNode) node;
        var scope: DynamicScope =  iVisited.getScope();
        
        // Serialization killed our dynamic scope.  We can just create an empty one
        // since serialization cannot serialize an eval (which is the only thing
        // which is capable of having a non-empty dynamic scope).
        if (scope == null) {
            scope = DynamicScope.newDynamicScope(iVisited.getStaticScope());
        }
        
        var staticScope: StaticScope =  scope.getStaticScope();
        
        // Each root node has a top-level scope that we need to push
        context.preScopedBody(scope);
        
        if (staticScope.getModule() == null) {
            staticScope.setModule(runtime.getObject());
        }

        try {
            return evalInternal(runtime, context, iVisited.getBodyNode(), self, aBlock);
        } finally {
            context.postScopedBody();
        }
    }

    private static function sClassNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: SClassNode =  (SClassNode) node;
        var receiver: IRubyObject =  evalInternal(runtime,context, iVisited.getReceiverNode(), self, aBlock);

        var singletonClass:RubyClass;

        if (receiver instanceof RubyFixnum || receiver instanceof RubySymbol) {
            throw runtime.newTypeError("no virtual class for " + receiver.getMetaClass().getBaseName());
        } else {
            if (runtime.getSafeLevel() >= 4 && !receiver.isTaint()) {
                throw runtime.newSecurityError("Insecure: can't extend object.");
            }

            singletonClass = receiver.getSingletonClass();
        }

        var scope: StaticScope =  iVisited.getScope();
        scope.setModule(singletonClass);
        
        return evalClassDefinitionBody(runtime, context, scope, iVisited.getBodyNode(), singletonClass, self, aBlock);
    }

    private static function splatNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        return splatValue(runtime, evalInternal(runtime, context, ((SplatNode) node).getValue(), self, aBlock));
    }

    private static function strNode(runtime:Ruby, node:Node):IRubyObject{
        return runtime.newStringShared((ByteList) ((StrNode) node).getValue());
    }
    
    private static function superNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: SuperNode =  (SuperNode) node;
   
        var klazz: RubyModule =  context.getFrameKlazz();
        
        if (klazz == null) {
            var name: String =  context.getFrameName();
            throw runtime.newNameError("Superclass method '" + name
                    + "' disabled.", name);
        }
        var args: Array =  setupArgs(runtime, context, iVisited.getArgsNode(), self, aBlock);
        var block: Block =  getBlock(runtime, context, self, aBlock, iVisited.getIterNode());
        
        // If no explicit block passed to super, then use the one passed in, unless it's explicitly cleared with nil
        if (iVisited.getIterNode() == null) {
            if (!block.isGiven()) block = aBlock;
        }
        
        return self.callSuper(context, args, block);
    }
    
    private static function sValueNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        return aValueSplat(runtime, evalInternal(runtime,context, ((SValueNode) node).getValue(), self, aBlock));
    }
    
    private static function symbolNode(runtime:Ruby, node:Node):IRubyObject{
        return ((SymbolNode)node).getSymbol(runtime);
    }
    
    private static function toAryNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        return aryToAry(runtime, evalInternal(runtime,context, ((ToAryNode) node).getValue(), self, aBlock));
    }

    private static function trueNode(runtime:Ruby, context:ThreadContext):IRubyObject{
        return pollAndReturn(context, runtime.getTrue());
    }
    
    private static function undefNode(runtime:Ruby, context:ThreadContext, node:Node):IRubyObject{
        var iVisited: UndefNode =  (UndefNode) node;
        var module: RubyModule =  context.getRubyClass();
   
        if (module == null) {
            throw runtime.newTypeError("No class to undef method '" + iVisited.getName() + "'.");
        }
        
        module.undef(context, iVisited.getName());
   
        return runtime.getNil();
    }

    private static function untilNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: UntilNode =  (UntilNode) node;
   
        var result: IRubyObject =  null;
        var firstTest: Boolean =  iVisited.evaluateAtStart();
        
        outerLoop: while (!firstTest || !(evalInternal(runtime,context, iVisited.getConditionNode(), self, aBlock)).isTrue()) {
            firstTest = true;
            loop: while (true) { // Used for the 'redo' command
                try {
                    evalInternal(runtime,context, iVisited.getBodyNode(), self, aBlock);
                    var loop:break;
                } catch (rj:JumpException.RedoJump) {
                    continue;
                } catch (nj:JumpException.NextJump) {
                    var loop:break;
                } catch (bj:JumpException.BreakJump) {
                    // JRUBY-530 until case
                    if (bj.getTarget() == aBlock.getBody()) {
                         bj.setTarget(null);

                         throw bj;
                    }

                    result = (IRubyObject) bj.getValue();

                    var outerLoop:break;
                }
            }
        }

        if (result == null) {
            result = runtime.getNil();
        }
        return pollAndReturn(context, result);
    }

    private static function valiasNode(runtime:Ruby, node:Node):IRubyObject{
        var iVisited: VAliasNode =  (VAliasNode) node;
        runtime.getGlobalVariables().alias(iVisited.getNewName(), iVisited.getOldName());
   
        return runtime.getNil();
    }

    private static function vcallNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject):IRubyObject{
        var iVisited: VCallNode =  (VCallNode) node;

        return iVisited.callAdapter.call(context, self);
    }

    private static function whileNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: WhileNode =  (WhileNode) node;
   
        var result: IRubyObject =  null;
        var firstTest: Boolean =  iVisited.evaluateAtStart();
        
        outerLoop: while (!firstTest || evalInternal(runtime,context, iVisited.getConditionNode(), self, aBlock).isTrue()) {
            firstTest = true;
            loop: while (true) { // Used for the 'redo' command
                try {
                    evalInternal(runtime,context, iVisited.getBodyNode(), self, aBlock);
                    var loop:break;
                } catch (re:RaiseException) {
                    if (runtime.fastGetClass("LocalJumpError").isInstance(re.getException())) {
                        var jumpError: RubyLocalJumpError =  (RubyLocalJumpError)re.getException();
                        
                        var reason: IRubyObject =  jumpError.reason();
                        
                        // admittedly inefficient
                        if (reason.asJavaString().equals("break")) {
                            return jumpError.exit_value();
                        } else if (reason.asJavaString().equals("next")) {
                            var loop:break;
                        } else if (reason.asJavaString().equals("redo")) {
                            continue;
                        }
                    }
                    
                    throw re;
                } catch (rj:JumpException.RedoJump) {
                    continue;
                } catch (nj:JumpException.NextJump) {
                    var loop:break;
                } catch (bj:JumpException.BreakJump) {
                    // JRUBY-530, while case
                    if (bj.getTarget() == aBlock.getBody()) {
                        bj.setTarget(null);

                        throw bj;
                    }

                    result = (IRubyObject) bj.getValue();
                    var outerLoop:break;
                }
            }
        }
        if (result == null) {
            result = runtime.getNil();
        }
        return pollAndReturn(context, result);
    }

    private static function xStrNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject):IRubyObject{
        return self.callMethod(context, "`", runtime.newStringShared((ByteList) ((XStrNode) node).getValue()));
    }

    private static function yieldNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var iVisited: YieldNode =  (YieldNode) node;
   
        var result: IRubyObject =  null;
        if (iVisited.getArgsNode() != null) {
            result = evalInternal(runtime, context, iVisited.getArgsNode(), self, aBlock);
        }

        var block: Block =  context.getCurrentFrame().getBlock();

        return block.yield(context, result, null, null, iVisited.getCheckState());
    }

    private static function zArrayNode(runtime:Ruby):IRubyObject{
        return runtime.newArray();
    }
    
    private static function zsuperNode(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):IRubyObject{
        var block: Block =  getBlock(runtime, context, self, aBlock, ((ZSuperNode) node).getIterNode());
        return RuntimeHelpers.callZSuper(runtime, context, block, self);
    }

    public static function aValueSplat(runtime:Ruby, value:IRubyObject):IRubyObject{
        if (!(value instanceof RubyArray) || ((RubyArray) value).length().getLongValue() == 0) {
            return runtime.getNil();
        }

        var array: RubyArray =  (RubyArray) value;

        return array.getLength() == 1 ? array.first(IRubyObject.NULL_ARRAY) : array;
    }

    private static function callTraceFunction(runtime:Ruby, context:ThreadContext, event:int):void{
        var name: String =  context.getFrameName();
        var type: RubyModule =  context.getFrameKlazz();
        runtime.callEventHooks(context, event, context.getFile(), context.getLine(), name, type);
    }

    private static function evalClassDefinitionBody(Ruby runtime, context:ThreadContext , StaticScope scope, 
            Node bodyNode, RubyModule type, IRubyObject self, Block block) {
        context.preClassEval(scope, type);

        try {
            if (isTrace(runtime)) {
                callTraceFunction(runtime, context, EventHook.RUBY_EVENT_CLASS);
            }

            return evalInternal(runtime,context, bodyNode, type, block);
        } finally {
            if (isTrace(runtime)) {
                callTraceFunction(runtime, context, EventHook.RUBY_EVENT_END);
            }
            
            context.postClassEval();
        }
    }

    private static function getArgumentDefinition(runtime:Ruby, context:ThreadContext, node:Node, type:String, self:IRubyObject, block:Block):String{
        if (node == null) return type;
            
        if (node instanceof ArrayNode) {
            for ( var int:i = 0; i < ((ArrayNode)node).size(); i++) {
                var iterNode: Node =  ((ArrayNode)node).get(i);
                if (getDefinitionInner(runtime, context, iterNode, self, block) == null) return null;
            }
        } else if (getDefinitionInner(runtime, context, node, self, block) == null) {
            return null;
        }

        return type;
    }
    
    public static function getBlock(runtime:Ruby, context:ThreadContext, self:IRubyObject, currentBlock:Block, blockNode:Node):Block{
        if (blockNode == null) return Block.NULL_BLOCK;
        
        if (blockNode instanceof IterNode) {
            var iterNode: IterNode =  (IterNode) blockNode;

            var scope: StaticScope =  iterNode.getScope();
            scope.determineModule();
            
            // Create block for this iter node
            // FIXME: We shouldn't use the current scope if it's not actually from the same hierarchy of static scopes
            return InterpretedBlock.newInterpretedClosure(context, iterNode.getBlockBody(), self);
        } else if (blockNode instanceof BlockPassNode) {
            var blockPassNode: BlockPassNode =  (BlockPassNode) blockNode;
            var proc: IRubyObject =  evalInternal(runtime,context, blockPassNode.getBodyNode(), self, currentBlock);
            
            return RuntimeHelpers.getBlockFromBlockPassBody(proc, currentBlock);
        }
         
        assert false: "Trying to get block from something which cannot deliver";
        return null;
    }


    public static function getClassVariableBase(context:ThreadContext, runtime:Ruby):RubyModule{
        var scope: StaticScope =  context.getCurrentScope().getStaticScope();
        var rubyClass: RubyModule =  scope.getModule();
        if (rubyClass.isSingleton()) {
            scope = scope.getPreviousCRefScope();
            rubyClass = scope.getModule();
            if (scope.getPreviousCRefScope() == null) {
                runtime.getWarnings().warn(ID.CVAR_FROM_TOPLEVEL_SINGLETON_METHOD, "class variable access from toplevel singleton method");
            }            
        }
        return rubyClass;
    }

    private static function getDefinition(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):String{
        try {
            context.setWithinDefined(true);
            return getDefinitionInner(runtime, context, node, self, aBlock);
        } finally {
            context.setWithinDefined(false);
        }
    }

    private static function getDefinitionInner(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):String{
        if (node == null) return "expression";
        
        switch(node.nodeId) {
        case ATTRASSIGNNODE: {
            var iVisited: AttrAssignNode =  (AttrAssignNode) node;
            
            if (getDefinitionInner(runtime, context, iVisited.getReceiverNode(), self, aBlock) != null) {
                try {
                    var receiver: IRubyObject =  eval(runtime, context, iVisited.getReceiverNode(), self, aBlock);
                    var metaClass: RubyClass =  receiver.getMetaClass();
                    var method: DynamicMethod =  metaClass.searchMethod(iVisited.getName());
                    var visibility: Visibility =  method.getVisibility();

                    if (visibility != Visibility.PRIVATE && 
                            (visibility != Visibility.PROTECTED || metaClass.getRealClass().isInstance(self))) {
                        if (metaClass.isMethodBound(iVisited.getName(), false)) {
                            return getArgumentDefinition(runtime,context, iVisited.getArgsNode(), "assignment", self, aBlock);
                        }
                    }
                } catch (excptn:JumpException) {
                }
            }

            return null;
        }
        case BACKREFNODE: {
            var backref: IRubyObject =  context.getCurrentFrame().getBackRef();
            if(backref instanceof RubyMatchData) {
                return "$" + ((BackRefNode) node).getType();
            } else {
                return null;
            }
        }
        case CALLNODE: {
            var iVisited: CallNode =  (CallNode) node;
            
            if (getDefinitionInner(runtime, context, iVisited.getReceiverNode(), self, aBlock) != null) {
                try {
                    var receiver: IRubyObject =  eval(runtime, context, iVisited.getReceiverNode(), self, aBlock);
                    var metaClass: RubyClass =  receiver.getMetaClass();
                    var method: DynamicMethod =  metaClass.searchMethod(iVisited.getName());
                    var visibility: Visibility =  method.getVisibility();

                    if (visibility != Visibility.PRIVATE && 
                            (visibility != Visibility.PROTECTED || metaClass.getRealClass().isInstance(self))) {
                        if (metaClass.isMethodBound(iVisited.getName(), false)) {
                            return getArgumentDefinition(runtime, context, iVisited.getArgsNode(), "method", self, aBlock);
                        }
                    }
                } catch (excptn:JumpException) {
                }
            }

            return null;
        }
        case CLASSVARASGNNODE: case CLASSVARDECLNODE: case CONSTDECLNODE:
        case DASGNNODE: case GLOBALASGNNODE: case LOCALASGNNODE:
        case MULTIPLEASGNNODE: case OPASGNNODE: case OPELEMENTASGNNODE:
            return "assignment";
            
        case CLASSVARNODE: {
            var iVisited: ClassVarNode =  (ClassVarNode) node;
            //RubyModule module = context.getRubyClass();
            var module: RubyModule =  context.getCurrentScope().getStaticScope().getModule();
            if (module == null && self.getMetaClass().fastIsClassVarDefined(iVisited.getName())) {
                return "class variable";
            } else if (module.fastIsClassVarDefined(iVisited.getName())) {
                return "class variable";
            } 
            var attached: IRubyObject =  null;
            if (module.isSingleton()) attached = ((MetaClass)module).getAttached();
            if (attached instanceof RubyModule) {
                module = (RubyModule)attached;

                if (module.fastIsClassVarDefined(iVisited.getName())) return "class variable"; 
            }

            return null;
        }
        case COLON3NODE:
        case COLON2NODE: {
            var iVisited: Colon3Node =  (Colon3Node) node;

            try {
                var left: IRubyObject =  runtime.getObject();
                if (iVisited instanceof Colon2Node) {
                    left = ASTInterpreter.eval(runtime, context, ((Colon2Node) iVisited).getLeftNode(), self, aBlock);
                }

                if (left instanceof RubyModule &&
                        ((RubyModule) left).fastGetConstantAt(iVisited.getName()) != null) {
                    return "constant";
                } else if (left.getMetaClass().isMethodBound(iVisited.getName(), true)) {
                    return "method";
                }
            } catch (excptn:JumpException) {}
            
            return null;
        }
        case CONSTNODE:
            if (context.getConstantDefined(((ConstNode) node).getName())) {
                return "constant";
            }
            return null;
        case DVARNODE:
            return "local-variable(in-block)";
        case FALSENODE:
            return "false";
        case FCALLNODE: {
            var iVisited: FCallNode =  (FCallNode) node;
            if (self.getMetaClass().isMethodBound(iVisited.getName(), false)) {
                return getArgumentDefinition(runtime, context, iVisited.getArgsNode(), "method", self, aBlock);
            }
            
            return null;
        }
        case GLOBALVARNODE:
            if (runtime.getGlobalVariables().isDefined(((GlobalVarNode) node).getName())) {
                return "global-variable";
            }
            return null;
        case INSTVARNODE:
            if (self.getInstanceVariables().fastHasInstanceVariable(((InstVarNode) node).getName())) {
                return "instance-variable";
            }
            return null;
        case LOCALVARNODE:
            return "local-variable";
        case MATCH2NODE: case MATCH3NODE:
            return "method";
        case NILNODE:
            return "nil";
        case NTHREFNODE: {
            var backref: IRubyObject =  context.getCurrentFrame().getBackRef();
            if(backref instanceof RubyMatchData) {
                ((RubyMatchData)backref).use();
                if(!((RubyMatchData)backref).group(((NthRefNode) node).getMatchNumber()).isNil()) {
                    return "$" + ((NthRefNode) node).getMatchNumber();
                }
            }
            return null;
        }
        case SELFNODE:
            return "self";
        case SUPERNODE: {
            var iVisited: SuperNode =  (SuperNode) node;
            var name: String =  context.getFrameName();
            var klazz: RubyModule =  context.getFrameKlazz();
            if (name != null && klazz != null && klazz.getSuperClass().isMethodBound(name, false)) {
                return getArgumentDefinition(runtime, context, iVisited.getArgsNode(), "super", self, aBlock);
            }
            
            return null;
        }
        case TRUENODE:
            return "true";
        case VCALLNODE: {
            var iVisited: VCallNode =  (VCallNode) node;
            if (self.getMetaClass().isMethodBound(iVisited.getName(), false)) {
                return "method";
            }
            
            return null;
        }
        case YIELDNODE:
            return aBlock.isGiven() ? "yield" : null;
        case ZSUPERNODE: {
            var name: String =  context.getFrameName();
            var klazz: RubyModule =  context.getFrameKlazz();
            if (name != null && klazz != null && klazz.getSuperClass().isMethodBound(name, false)) {
                return "super";
            }
            return null;
        }
        default:
            try {
                ASTInterpreter.eval(runtime, context, node, self, aBlock);
                return "expression";
            } catch (jumpExcptn:JumpException) {}
        }
        
        return null;
    }

    private static function getEnclosingModule(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, block:Block):RubyModule{
        var enclosingModule: RubyModule =  null;

        if (node instanceof Colon2Node) {
            var result: IRubyObject =  evalInternal(runtime,context, ((Colon2Node) node).getLeftNode(), self, block);

            if (result != null && !result.isNil()) {
                enclosingModule = (RubyModule) result;
            }
        } else if (node instanceof Colon3Node) {
            enclosingModule = runtime.getObject();
        }

        if (enclosingModule == null) {
            enclosingModule = context.getCurrentScope().getStaticScope().getModule();
        }

        return enclosingModule;
    }


    private static function isTrace(runtime:Ruby):Boolean{
        return runtime.hasEventHooks();
    }

    private static function setupArgs(runtime:Ruby, context:ThreadContext, node:Node, self:IRubyObject, aBlock:Block):Array{
        if (node == null) return IRubyObject.NULL_ARRAY;

        if (node instanceof ArrayNode) {
            var argsArrayNode: ArrayNode =  (ArrayNode) node;
            var savedFile: String =  context.getFile();
            var savedLine: int =  context.getLine();
            var size: int =  argsArrayNode.size();
            var argsArray: Array =  new IRubyObject[size];

            for ( var int:i = 0; i < size; i++) {
                argsArray[i] = evalInternal(runtime,context, argsArrayNode.get(i), self, aBlock);
            }

            context.setFile(savedFile);
            context.setLine(savedLine);

            return argsArray;
        }

        return ArgsUtil.convertToJavaArray(evalInternal(runtime,context, node, self, aBlock));
    }
    
*/

    public static function arrayValue(runtime:Ruby, value:IRubyObject):RubyArray{
        var tmp: IRubyObject =  value.checkArrayType();

        if (tmp.isNil()) {
            // Object#to_a is obsolete.  We match Ruby's hack until to_a goes away.  Then we can 
            // remove this hack too.
/*             if (value.getMetaClass().searchMethod("to_a").getImplementationClass() != runtime.getKernel()) {
                value = value.callMethod(runtime.getCurrentContext(), MethodIndex.TO_A, "to_a");
                if (!(value instanceof RubyArray)) throw runtime.newTypeError("`to_a' did not return Array");
                return RubyArray(value);
            } else { */
                return runtime.newArray2(value);
            //}
        }
        return RubyArray(tmp);
    }

    public static function splatValue(runtime:Ruby, value:IRubyObject):RubyArray{
        if (value.isNil()) {
            return runtime.newArray2(value);
        }

        return arrayValue(runtime, value);
    }


    // Used by the compiler to simplify arg processing
    public static function splatValue2(value:IRubyObject, runtime:Ruby):RubyArray{
        return splatValue(runtime, value);
    }
/*     public static function aValueSplat(value:IRubyObject, runtime:Ruby):IRubyObject{
        return aValueSplat(runtime, value);
    }
    public static function aryToAry(value:IRubyObject, runtime:Ruby):IRubyObject{
        return aryToAry(runtime, value);
    } */
}
}